Skip to content

feat(runtime): 端点映射键 —— inputMapping / outputMapping 链内应用(#5040 E5c) - #5167

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5137-endpoint-mappings
Aug 4, 2026
Merged

feat(runtime): 端点映射键 —— inputMapping / outputMapping 链内应用(#5040 E5c)#5167
os-zhuang merged 1 commit into
mainfrom
claude/issue-5137-endpoint-mappings

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5137
Part of #5040(E5c),Blocked-by #5129(E5b,已在 main:81e2744)

问题

inputMapping / outputMappingApiEndpointSchema 声明,被 runtime 读取零次:作者写了、publish 放行、端点跑起来映射什么也不做。这正是 #5040 开篇要消灭的「解析通过然后什么也不发生」中间态,也是 ADR-0049 declared ≠ enforced 的教科书形状 —— 对 AI 写的元数据尤其糟,静默忽略的键不产生任何信号,错误会一直留在应用里。

语义的唯一依据:冻结词表的 describe 文本

词表冻结(#5040),所以这两个键意味着 packages/spec/src/api/endpoint.zod.ts 说的那几句:

声明 .describe()
inputMapping Map Request Body to Internal Params
outputMapping Map Internal Result to Response Body
ApiMapping.source / .target Source field/path / Target field/path
ApiMapping.transform Transformation function name

本 PR 取其最小忠实解读;文本沉默处一律取表达力最低的一种,并在模块文档里逐条写明为什么 —— 反面是在 runtime 里造一套没有契约声明、publish 门也检查不到的方言。每条选择都是日后可放宽、不可收紧的方向。

  1. 映射是投影(projection),不是合并。 结果只由声明的 target 组成,未声明字段不随行 —— 这既是「map A to B」的字面意思,也是词表自带示例的行为(firstNamefirst_nameuser.profile.emailcontact.email,见 endpoint.test.ts)。出站方向因此天然是一份 allow-list,而 apis 是平台的对外面(ADR-0121 D3):默认把管线返回的每个内部字段都漏出去,不是可接受的缺省。
  2. source 读的是请求体,不是 query。 词表写的是 Map Request Body to Internal ParamsE5 遗漏面:端点 inputMapping / outputMapping 未实现(设计 §3.4 的 api-mapping.ts),E7 翻转前必须补 #5137 正文与设计 §3.4 曾提到 {...query, ...body},本 PR 采纳:合并两者会凭空发明一条「谁覆盖谁」的优先级规则,而一条没人写下来的优先级规则,作者只能靠请求行为与声明读起来不一致才发现。query 参数照旧原样抵达管线。若维护者认为映射也应看见 query,那是对本解读的兼容放宽(加一个来源),反过来则是破坏性收紧 —— 这也是先取窄的原因。
  3. source / target 是点路径,仅此而已。 a.b.c. 切分,只读自有属性(路径永远够不着原型链成员),数组元素按数字键寻址(records.0.id)。无通配、无过滤、无 $ 语法、无转义 —— 「键里真的带点」不可寻址,这是词表的限制,不是在这里设计一套的许可。
  4. source 解析不到 ⇒ target 不写。 映射是投影不是校验器:缺省的可选字段得到「目标缺席」,而不是显式 null 键或被拒绝的请求。在这里发明必填规则,等于把目标管线已用对象元数据做的校验抄一份更弱的。
  5. 无声明(或空数组)⇒ 逐字节直通、按引用原样传递。 未声明映射的端点与 E5b 的行为完全一致,两侧都有测试钉住。

应用点(设计 §3 的顺序)

落在 api-endpoint-step.ts,endpoint-executor.ts 保持纯委派、对映射无感知:

  • inputMapping:策略链通过之后、委派之前 —— 投影执行器看到的那个请求,所以映射永远买不通 authRequired / rateLimit;
  • outputMapping:只作用于成功答案的载荷({success, data, meta}data),包络逐字保留。401 / 429 / 400 / 501 一律不重映射 —— 能把失败重塑成数据的投影,就能把失败藏起来。与 Cache-Control 只上成功答案是同一条不对称,理由更强。

无法服务的声明:响亮拒绝,不静默跳过

结构化 501 NOT_IMPLEMENTED,点名具体条目(如 inputMapping[1].transform),带处方:

  • transform —— 全仓不存在「transformation function name」注册表,发明一个是沙箱裁决而非映射细节(stack.zod.ts 同名先例:framework#2611 令其 build 期失败)。17.x 立项:建设声明式 ApiEndpoint 执行器(挂载 + matchEndpoint + authRequired/cacheTtl/inputMapping/outputMapping 逐键接线) #5040 §3.4 维持 publish 拒绝,本模块是「绕过 publish 直接 metadata.register()」的运行期兜底;
  • 不可用路径 —— 空串、空段(a..b)、JS 原型键(__proto__ / prototype / constructor,两侧都不可走,声明日益由 AI 书写,不能当作可信到能写原型的来源);
  • 互撞的 target —— 两条写同一路径,或一条写进另一条内部,后者会静默丢弃前者。

状态码说明(与派发词的一处偏离,请复核):派发词建议「400 类」;本 PR 用 501 + NOT_IMPLEMENTED,与 endpoint-executor.tsunsupported 分支(script / proxy / 缺 objectParams)完全同类同形 —— 这是同一个范畴的答案(冻结词表中 17.x 不执行的子集),且调用方没有做错任何事,用 4xx 会把缺陷归错人。改回 4xx 只需动一个常量,由 PM 裁定。

outputMapping 的这道判定在委派之前做:投影坏掉的 create 不该先插入记录、再拒绝作答。

验证

pnpm --filter @objectstack/runtime test        → 89 files / 1312 tests passed
pnpm --filter @objectstack/runtime typecheck   → tsc --noEmit,零输出
pnpm check:nul-bytes                           → OK(5235 tracked text files,无 NUL)
pnpm check:route-envelope                      → 8 route modules / 16 domains,0 ratcheted
pnpm check:error-code-casing                   → 2796 files,无小写错误码(ADR-0112)
pnpm --filter @objectstack/spec check:generated → 8/8 up to date(生成物零移动)
npx eslint <5 个改动文件>                       → 零输出

既有测试一行未改(api-endpoint-step.test.ts 只有新增 208 行,0 删除)—— 无声明映射时答案不变,这是本单的验收前提。api-mapping.ts 已加入 error-envelope.conformance.test.ts 的源码扫描名单(与 E4/E5 同处不同行,合并友好)。

现网影响

零。非空 apis: 在 publish / validate 仍被硬拒(E7 #5111 前不撤),整条端点链结构性不可达。

交给 E7(#5111)的两条

  1. 上述「不支持子集」(transform、不可用路径、互撞 target)必须进 publish 门带处方拒绝 —— 本模块是兜底,不该是作者遇到的第一道关口;
  2. 建议一并考虑:inputMapping 声明在不读 body 的操作(find / get / delete)上时,今天是一条什么也不做的合法声明 —— 是否在 publish 期拒绝,属词表/门的裁决,runtime 侧不擅自发明。

🤖 Generated with Claude Code

https://claude.ai/code/session_01EYGdmvWP1ieZSLqvAW6uyd


Generated by Claude Code

…#5137)

`inputMapping` / `outputMapping` were declared by `ApiEndpointSchema` and read
by nothing: an author could write them, publish would accept them, and the
endpoint ran as if they were absent — the "parsed, then nothing happens" middle
state #5040 exists to end, and the ADR-0049 `declared != enforced` shape.

New pure module `packages/runtime/src/api-mapping.ts` is their single reader.
Semantics come from the frozen vocabulary's describe text and nothing else,
taken in its minimal faithful reading:

- `inputMapping` ("Map Request Body to Internal Params") projects the request
  BODY by dot path, applied after the policy pass and before delegation, so a
  mapping can never buy a caller past `authRequired` / `rateLimit` and
  `endpoint-executor.ts` stays a pure delegator. Query params are deliberately
  NOT merged in — the vocabulary names the body, and merging would invent an
  unstated precedence rule.
- `outputMapping` ("Map Internal Result to Response Body") projects the SUCCESS
  payload only, preserving the envelope; an error answer is never remapped, so
  a declaration cannot disguise a failure as data.
- A mapping is a projection, not a merge: undeclared fields do not ride along,
  which makes the outbound side an allow-list.
- Absent source => unset target; absent (or empty) key => byte-for-byte
  passthrough, by reference.
- A declaration this runtime cannot serve is refused with a structured 501
  NOT_IMPLEMENTED naming the entry — `transform` (no transformation-function
  registry exists), an unusable path (empty, empty segment, prototype key), or
  colliding targets. `outputMapping` is judged BEFORE delegation so a broken
  projection cannot let a `create` insert its record and then fail to answer.

`api-mapping.ts` joins the error-envelope conformance scan. Zero live behavior
change: a non-empty `apis:` is still rejected at publish until the E7 flip.

Part of #5040 (E5c).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYGdmvWP1ieZSLqvAW6uyd
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 4, 2026 7:20am

Request Review

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/runtime.

21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx (via packages/runtime)
  • content/docs/api/index.mdx (via @objectstack/runtime)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/runtime)
  • content/docs/concepts/north-star.mdx (via packages/runtime)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/runtime)
  • content/docs/kernel/cluster.mdx (via @objectstack/runtime)
  • content/docs/permissions/authentication.mdx (via @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/runtime)
  • content/docs/plugins/packages.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime)
  • content/docs/releases/implementation-status.mdx (via @objectstack/runtime)
  • content/docs/releases/v17.mdx (via @objectstack/runtime)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review August 4, 2026 07:21
@os-zhuang
os-zhuang enabled auto-merge August 4, 2026 07:21
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit 41e605e Aug 4, 2026
24 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5137-endpoint-mappings branch August 4, 2026 07:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

E5 遗漏面:端点 inputMapping / outputMapping 未实现(设计 §3.4 的 api-mapping.ts),E7 翻转前必须补

2 participants